package pers.mihao.quickstream.common.util.spring;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.util.ReflectionUtils;
import pers.mihao.quickstream.common.exception.BaseException;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author mihao
 * @since 2023/3/6
 */
public class SpringUtil {

    public static Method createBeanMethod = null;


    public static <T> T getBean(Class<T> clazz) {
        return ApplicationContextHolder.getApplicationContext().getBean(clazz);
    }

    public static <T> T getBeanOrNull(Class<T> clazz) {
        try {
            return ApplicationContextHolder.getBeanOrNull(clazz);
        } catch (BeansException | BaseException e) {
            return null;
        }
    }


    public static Object createBeanWrapper(BeanFactory beanFactory, Class<?> tClass, String beanName, Object... arg) {
        Method method = getDoCreateBeanMethod(beanFactory);
        Object bean = ReflectionUtils.invokeMethod(method, beanFactory, beanName, new RootBeanDefinition(tClass), arg);
        if (beanFactory instanceof DefaultListableBeanFactory) {
            invokeAwareMethods(
                    bean, (DefaultListableBeanFactory) beanFactory);
        }
        return bean;
    }

    public static void invokeAwareMethods(Object parserStrategyBean,
                                          BeanDefinitionRegistry registry) {

        if (parserStrategyBean instanceof Aware) {
            if (parserStrategyBean instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(ApplicationContextHolder.getClassLoader());
            }
            if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
                ((BeanFactoryAware) parserStrategyBean).setBeanFactory(ApplicationContextHolder.getBeanFactory());
            }
            if (parserStrategyBean instanceof EnvironmentAware) {
                ((EnvironmentAware) parserStrategyBean).setEnvironment(ApplicationContextHolder.getEnvironment());
            }
            if (parserStrategyBean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(ApplicationContextHolder.getResourceLoader());
            }
        }
    }

    public static Object createBeanWrapper(Class<?> tClass, String beanName, Object... arg) {
        ConfigurableListableBeanFactory beanFactory = getConfigurableListableBeanFactory();
        Method method = getDoCreateBeanMethod(beanFactory);
        return ReflectionUtils.invokeMethod(method, beanFactory, beanName, new RootBeanDefinition(tClass), arg);
    }


    public static <T> T createBean(String beanName, Class<T> clazz) {
        ConfigurableListableBeanFactory beanFactory = getConfigurableListableBeanFactory();
        T oldBean = (T) beanFactory.getSingleton(beanName);
        if (oldBean != null) {
            return oldBean;
        }
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(clazz);
        try {
            Object bean = getDoCreateBeanMethod().invoke(beanFactory, beanName, beanDefinition, new Object[]{});
            beanFactory.registerSingleton(beanName, bean);
            return (T) bean;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Method getDoCreateBeanMethod(BeanFactory beanFactory) {
        if (createBeanMethod == null) {
            synchronized (SpringUtil.class) {
                if (createBeanMethod != null) {
                    return createBeanMethod;
                }
                List<Method> methods = Arrays.stream(ReflectionUtils.getAllDeclaredMethods(beanFactory.getClass()))
                        .filter(method -> "doCreateBean".equals(method.getName())).collect(Collectors.toList());
                for (Method method : methods) {
                    if (method.getParameterCount() == 3 && method.getParameterTypes()[0] == String.class
                            && method.getParameterTypes()[1] == RootBeanDefinition.class
                            && method.getParameterTypes()[2] == Object[].class) {
                        createBeanMethod = method;
                        createBeanMethod.setAccessible(true);
                        break;
                    }
                }
            }
        }

        if (createBeanMethod == null) {
            throw new RuntimeException("Not Support Spring Version Need Bean " +
                    "Factory Imp Has Method doCreateBean()");
        }
        return createBeanMethod;
    }


    private static Method getDoCreateBeanMethod() {
        ConfigurableListableBeanFactory beanFactory = getConfigurableListableBeanFactory();
        return getDoCreateBeanMethod(beanFactory);
    }

    private static ConfigurableListableBeanFactory getConfigurableListableBeanFactory() {
        ConfigurableApplicationContext refreshableApplicationContext = (ConfigurableApplicationContext)
                ApplicationContextHolder.getApplicationContext();
        ConfigurableListableBeanFactory beanFactory = refreshableApplicationContext.getBeanFactory();
        return beanFactory;
    }

    private static SingletonBeanRegistry getSingletonBeanRegistryFactory() {
        ConfigurableApplicationContext refreshableApplicationContext = (ConfigurableApplicationContext)
                ApplicationContextHolder.getApplicationContext();
        SingletonBeanRegistry beanFactory = refreshableApplicationContext.getBeanFactory();
        return beanFactory;
    }

}
